package com.enjoy.service;

import com.alibaba.fastjson.JSON;
import com.enjoy.common.RedisKeyPrefixConst;
import com.enjoy.common.RedisUtil;
import com.enjoy.dao.ProductDao;
import com.enjoy.model.Product;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Component
public class ProductService {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private ProductDao productDao;

    @Autowired
    private Redisson redisson;

    public static final int PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;
    public static final String EMPTY_CACHE = "{}";
    public static final String LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX = "lock:product:hot:cache:crate_prefix:";
    public static final String LOCK_PRODUCT_HOT_CACHE_UPDATE_PREFIX = "lock:product:hot:cache:update_prefix:";
    private Map<String, Product> productMap = new HashMap<>();

    public Product update(Product product) {
//        RLock rLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_UPDATE_PREFIX);
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_HOT_CACHE_UPDATE_PREFIX);
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        Product updateProduct;
        try {
            updateProduct = productDao.update(product);
            redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + product.getId(), JSON.toJSONString(updateProduct), genProductCacheTimeout(), TimeUnit.SECONDS);
        } finally {
            rLock.unlock();
        }

        return updateProduct;
    }


    public Product get(long productId) {

        Product product = null;

        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;

        product = getProductFromCache(productCacheKey);

        if (product != null) {
            return product;
        }

        RLock lock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX + productId);
        lock.lock();

        try {
            product = getProductFromCache(productCacheKey);

            if (product != null) {
                return product;
            }

//            RLock rLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_UPDATE_PREFIX);
            RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_HOT_CACHE_UPDATE_PREFIX);
            RLock rLock = readWriteLock.readLock();
            rLock.lock();
            try {
                product = productDao.get(productId);
                if (product != null) {
                    // 防止浪费redis空间
                    redisUtil.set(productCacheKey, JSON.toJSONString(product), PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genProductEmptyCacheTimeout(), TimeUnit.SECONDS);
                }
            } finally {
                rLock.unlock();
            }
        } finally {
            lock.unlock();
        }

        return product;
    }


    private Product getProductFromCache(String productCacheKey) {
        // 本地缓存
        Product product = productMap.get(productCacheKey);
        if (product != null) {
            return product;
        }
        String productStr = redisUtil.get(productCacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            // 防止缓存击穿
            if (EMPTY_CACHE.equalsIgnoreCase(productStr)) {
                redisUtil.expire(productCacheKey, genProductEmptyCacheTimeout(), TimeUnit.SECONDS);
                return new Product();
            }
            // 读延期
            redisUtil.expire(productCacheKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
            return JSON.parseObject(productStr, Product.class);
        }
        return null;
    }

    public long genProductCacheTimeout() {
        return new Random().nextInt(5 * PRODUCT_CACHE_TIMEOUT);
    }

    public long genProductEmptyCacheTimeout() {
        return new Random().nextInt(5 * 60);
    }
}
